#!/usr/bin/env python
# -*- coding: utf-8 -*-

from threading import Thread, Event, Lock
from Queue import Queue, Empty
import time
from trivmoo.utils.decorators import synchronized
import unittest

#TODO: study this package
#from multiprocessing.pool import ThreadPool

## Task interface definition       
class ITask(object):
    ## execute the code of the task
    def execute(self):
        pass
    
## Task Queue interface definition
# a task queue contains a list of task to execute
class ITaskQueue(object):
    ## return next task to execute
    def get_next_task(self):
        pass
    
    ## add task to the queue
    def add_task(self, atask):
        pass

## thread pool class
class ThreadPool(object):
    def __init__(self, thread_number, task_queue):
        self.__thread_list = []
        # create working thread list
        for i in range(thread_number):
            self.__thread_list.append(WorkThread(task_queue, thread_name='Pool-Thread-%s' % (str(i))))
    
    ## stop all thread in pool
    def stop_pool(self):
        # ask for all thread stop
        for athread in self.__thread_list:
            athread.stop()
        # wait for thread stop
        for athread in self.__thread_list:
            athread.join()
            
    ## start all thread in pool
    def start_pool(self):
        # ask for thread to start
        for athread in self.__thread_list:
            athread.start()
            
    ## get the thread number in the pool
    def get_thread_number(self):
        return len(self.__thread_list)
    
    ## get the number of running threads in the pool
    def get_running_thread_number(self):
        count = 0
        for athread in self.__thread_list:
            if (athread.isAlive()):
                count = count + 1                
        return count

## Thread pool init exception
class WorkThreadInitException(RuntimeError):
    pass
          
## WorkingThread Class  
class WorkThread(Thread):
    def __init__(self, task_queue, thread_name=None, time_wait=1):
        if not task_queue:
            raise WorkThreadInitException("Unable to init work thread: no task queue specified")
        if not isinstance(task_queue, ITaskQueue):
            raise WorkThreadInitException("Unable to init work thread: need ITaskQueue parameter")
        
        Thread.__init__(self, name=thread_name)
        
        self.__stop_event = Event()
        self.__task_queue = task_queue
        self.__time_wait = time_wait
    
    ## thread main function
    def run(self):
        # reset stop event
        self.__stop_event.clear()
        while True:
            try:
                if self.__stop_event.isSet():
                    # stop event is setted, stop the thread ...
                    break
                task = self.__task_queue.get_next_task()
                if task:
                    # execute this task
                    task.execute()
                else:
                    # no task available found in the task list, just wait few seconds ...
                    time.sleep(self.__time_wait)
            except StandardError, ex:
                self.manage_task_exception(ex)
            except:
                pass
                
    def manage_task_exception(self, exception):
        # by default, silently ignore the exception. Override if necessary (better...)
        pass

    ## stop the thread
    def stop(self):
        # set stop event
        self.__stop_event.set()
        
## lock on taskqueue
TASK_QUEUE_LOCK = Lock()

## TaskQueue init exception
class TaskQueueException(RuntimeError):
    pass

## Basic task queue exception
class BasicTaskQueue(ITaskQueue):
    def __init__(self):
        ITaskQueue.__init__(self)
        self.__task_queue = Queue()
        
    def is_empty(self):
        return self.__task_queue.empty()
    
    ## return next task to execute
    @synchronized(TASK_QUEUE_LOCK)  #TODO: is this really necessary because of the GIL?
    def get_next_task(self):
        try:
            atask = self.__task_queue.get_nowait()
            if atask:
                return atask                
        except Empty:
            # the list may be empty
            pass        
        return None
    
    ## add task to the queue
    # the task must respect the ITask interface
    @synchronized(TASK_QUEUE_LOCK)  #TODO: is this really necessary because of the GIL?
    def add_task(self, atask):
        if not atask:
            raise TaskQueueException("Unable to add task in queue: no task specified")
        if not isinstance(atask, ITask):
            raise TaskQueueException("Unable to add task in queue: task parameter is not of task type")
        self.__task_queue.put_nowait(atask)
          
class TestThreadPool(unittest.TestCase):
    def test_init(self):
        """Thread pool initialization"""        
        thread_pool = ThreadPool(5, BasicTaskQueue())
        self.assertEqual(thread_pool.get_thread_number(), 5)
        try:
            thread_pool = ThreadPool(10, None)
            self.assertTrue(False)
        except WorkThreadInitException:
            pass
        try:
            thread_pool = ThreadPool(10, "aaaa")
            self.assertTrue(False)
        except WorkThreadInitException:
            self.assertTrue(True)
        
        
    def test_start_and_stop(self):
        """Start and stop of thread pool"""
        thread_pool = ThreadPool(3, BasicTaskQueue())
        self.assertEqual(thread_pool.get_thread_number(), 3)
        thread_pool.start_pool()
        self.assertEqual(thread_pool.get_running_thread_number(), 3)
        thread_pool.stop_pool()
        self.assertEqual(thread_pool.get_running_thread_number(), 0)
        

if __name__ == "__main__":
    unittest.main()
    